/*
 * Teletext decoding for ffmpeg
 * Copyright (c) 2005-2010, 2012 Wolfram Gloger.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "avcodec.h"
#include "libavutil/opt.h"

#include <libzvbi.h>

//#define DEBUG         1

#define TEXT_MAXSZ    2044
#define BINARIZE      0
#define PALETTE_MAX   40
#define RGBA(r,g,b,a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
#define VBI_R(rgba)   (((rgba) >> 0) & 0xFF)
#define VBI_G(rgba)   (((rgba) >> 8) & 0xFF)
#define VBI_B(rgba)   (((rgba) >> 16) & 0xFF)
#define VBI_A(rgba)   (((rgba) >> 24) & 0xFF)

/* main data structure */
typedef struct TeletextContext
{
    AVClass        *class;
    char           *pgno;
    int             y_offset;
    int             ci_clip;
    int             gen_bitmap;
    int             force_chop_top;
    int             sub_display_time; /* in msec */

    int             lines_processed;
    int             last_rows, last_cols;
    AVSubtitleRect *sub_rect;

    vbi_decoder *   vbi;
    vbi_dvb_demux * dx;
#ifdef DEBUG
    vbi_export *    ex;
#endif
    /* Don't even _think_ about making sliced stack-local! */
    vbi_sliced      sliced[64];
} TeletextContext;

/************************************************************************/

static int
teletext_decode_frame(AVCodecContext *avctx,
                      void *data, int *data_size,
                      AVPacket *pkt)
{
    TeletextContext *ctx = avctx->priv_data;
    AVSubtitle      *sub = data;
    const uint8_t   *buf = pkt->data;
    unsigned int    left = pkt->size;

    while (left > 0) {
        int64_t pts = 0;
        unsigned int lines = vbi_dvb_demux_cor(ctx->dx, ctx->sliced, 64, &pts, &buf, &left);
#ifdef DEBUG
        av_log(avctx, AV_LOG_INFO,
               " Teletext: ctx=%p buf_size=%d left=%u lines=%u pts=%f\n",
               ctx, pkt->size, left, lines, (double)pts/90000.0);
#endif
        if (lines > 0) {
#ifdef DEBUGx
            int i;
            for(i=0; i<lines; ++i)
                if (ctx->sliced[i].id >= 0)
                    av_log(avctx, AV_LOG_INFO,
                           " Teletext: lines=%d id=%x\n", i, ctx->sliced[i].id);
#endif
            vbi_decode(ctx->vbi, ctx->sliced, lines, (double)pts/90000.0);
            ctx->lines_processed += lines;
        }
    }

    // is there a subtitle to pass?
    if (ctx->sub_rect) {
        sub->format = 0;
        sub->start_display_time = 0;
        sub->end_display_time = ctx->sub_display_time;
        sub->num_rects = 0;

        if (ctx->sub_rect->type != SUBTITLE_NONE) {
            sub->rects = av_malloc(sizeof(*sub->rects) * 1);
            if (sub->rects) {
                sub->num_rects = 1;
                sub->rects[0] = ctx->sub_rect;
            }
        } else {
            av_log(avctx, AV_LOG_DEBUG, " Teletext: sending empty sub\n");
            sub->rects = 0;
        }
        if (!sub->rects) // no rect was passed
            av_free(ctx->sub_rect);
        ctx->sub_rect = 0;

        *data_size = 1;
    } else
        *data_size = 0;

    return pkt->size;
}

static int
chop_spaces_utf8(const unsigned char* t, int len)
{
    t += len;
    while (len > 0) {
        if (*--t != ' ' || (len-1 > 0 && *(t-1) & 0x80))
            break;
        --len;
    }
    return len;
}

// draw a page as text
static int
gen_sub_text(TeletextContext *ctx, vbi_page *page, int chop_top)
{
    AVSubtitleRect *sub_rect = ctx->sub_rect;
    char *text;
    const char *in;
    char *out;
    char *vbi_text = av_malloc(TEXT_MAXSZ);
    int sz = vbi_print_page_region(page, vbi_text, TEXT_MAXSZ-1, "UTF-8",
                                   /*table mode*/ TRUE, FALSE,
                                   0,             chop_top,
                                   page->columns, page->rows-chop_top);
    if (sz <= 0) {
        av_log(0, AV_LOG_ERROR, "vbi_print error\n");
        av_free(vbi_text);
        return -1;
    }
    vbi_text[sz] = '\0';
    in  = vbi_text;
    out = text = av_malloc(TEXT_MAXSZ);
    for (;;) {
        int nl, sz;

        // skip leading spaces and newlines
        in += strspn(in, " \n");
        // compute end of row
        for (nl = 0; in[nl]; ++nl)
            if (in[nl] == '\n' && (nl==0 || !(in[nl-1] & 0x80)))
                break;
        if (!in[nl])
            break;
        // skip trailing spaces
        sz = chop_spaces_utf8(in, nl);
        memcpy(out, in, sz);
        out += sz;
        *out++ = '\n';
        in  += nl;
    }
    av_free(vbi_text);
    *out = '\0';
    if (out > text) {
        sub_rect->type = SUBTITLE_TEXT;
        sub_rect->text = text;
        av_log(0, AV_LOG_DEBUG, "subtext:%s:txetbus\n", text);
    } else {
        sub_rect->type = SUBTITLE_NONE;
        av_free(text);
    }
    return 0;
}

static int
find_ci_in_page(const vbi_rgba *pix, const vbi_page *page)
{
    static int last_i = 0; // cache for performance

    if (*pix == page->color_map[last_i])
        return last_i;
    for (last_i=0;
         last_i<(sizeof page->color_map)/(sizeof page->color_map[0]);
         ++last_i) {
        if (*pix == page->color_map[last_i])
            return last_i;
    }
    return -1;
}

// draw a page as bitmap
static int
gen_sub_bitmap(TeletextContext *ctx, vbi_page *page, int chop_top)
{
    AVSubtitleRect *sub_rect = ctx->sub_rect;
    int resx = page->columns*12, resy = page->rows*10;
    int ix, iy, ci, ci_max = 0, ci_max_count = 0;
    const vbi_rgba* vptr;
    uint8_t *bptr;
    char *canvas = av_malloc(resx*resy*sizeof(vbi_rgba));

    if (!canvas)
        return -1;

    /* only VBI_PIXFMT_RGBA32_LE is supported in libzvbi */
    vbi_draw_vt_page(page, VBI_PIXFMT_RGBA32_LE, canvas,
                     /*reveal*/ 1, /*flash*/ 1);

    if (chop_top) {
        resy -= 10;
        av_log(0, AV_LOG_DEBUG, " Teletext: chopping top line\n");
    }
    //avpicture_alloc(&sub_rect->pict, PIX_FMT_PAL8, resx, resy);
    sub_rect->pict.data[0] = av_mallocz(resx*resy);

    vptr = (const vbi_rgba *)canvas + (chop_top*10*resx);
    bptr = sub_rect->pict.data[0];

    for (iy=0; iy<resy; ++iy) {
        for (ix=0; ix<resx; ++ix, ++vptr, ++bptr) {
            ci = find_ci_in_page(vptr, page);
            if (ci < 0)
                av_log(0, AV_LOG_ERROR,
                       " Teletext: can't find pixel in LUT!\n");
            else {
                if (ci >= ctx->ci_clip)
                    ci = ci % ctx->ci_clip;
                if (ci > ci_max)
                    ci_max = ci;
                if (ci == ci_max)
                    ++ci_max_count;
                *bptr = BINARIZE ? (!!ci) : (uint8_t)ci;
            }
        }
    }
    av_free(canvas);

    if (ci_max_count == resx*resy) {
        av_log(0, AV_LOG_DEBUG, " Teletext: dropping empty page\n");
        // send empty subtitle
        //avpicture_free(&sub_rect->pict);
        av_freep(&sub_rect->pict.data[0]);
        sub_rect->type = SUBTITLE_NONE;
    } else {
        sub_rect->x = resx<720 ? (720 - resx)/2 : 0;
        sub_rect->y = resy<576 ? (576 - resy) : 0;
        if (sub_rect->y > ctx->y_offset)
            sub_rect->y -= ctx->y_offset;
        sub_rect->w = resx;
        sub_rect->h = resy;
        sub_rect->nb_colors = ci_max+1;
        sub_rect->pict.linesize[0] = resx;
        sub_rect->pict.data[1] = av_malloc(sub_rect->nb_colors * sizeof(uint32_t));
        //sub_rect->rgba_palette =
        //    av_malloc(sizeof(*sub_rect->rgba_palette)*
        //              sub_rect->nb_colors);
        for (ci=0; ci<sub_rect->nb_colors; ++ci) {
            int r, g, b, a;

            r = VBI_R(page->color_map[ci]);
            g = VBI_G(page->color_map[ci]);
            b = VBI_B(page->color_map[ci]);
            a = (r+g+b == 0) ? 0 : VBI_A(page->color_map[ci]);
            ((uint32_t *)sub_rect->pict.data[1])[ci] =
                RGBA(r, g, b, a);
#ifdef DEBUG
            av_log(0, AV_LOG_DEBUG, " Teletext: palette %0x\n",
                   ((uint32_t *)sub_rect->pict.data[1])[ci]);
#endif
        }
        sub_rect->type = SUBTITLE_BITMAP;
    }
#ifdef DEBUG
    fprintf(stderr, "ci_max=%d...\n", ci_max);
#endif
    return 0;
}

static void
handler(vbi_event *ev, void *user_data)
{
    TeletextContext *ctx = user_data;
    vbi_page page;
    int res;
    char pgno_str[12];

    snprintf(pgno_str, sizeof pgno_str, "%03x", ev->ev.ttx_page.pgno);
    av_log(0, AV_LOG_DEBUG, "Page %s.%02x %s\n",
           pgno_str, ev->ev.ttx_page.subno & 0xFF, ctx->pgno);

    if (strcmp(ctx->pgno, "*") && !strstr(ctx->pgno, pgno_str)) {
        return;
    }

    /* Fetch the page.  */
    res = vbi_fetch_vt_page(ctx->vbi, &page,
                            ev->ev.ttx_page.pgno,
                            ev->ev.ttx_page.subno,
                            VBI_WST_LEVEL_3p5, 25, TRUE);

#ifdef DEBUG
    fprintf(stderr, "\nSaving res=%d dy0=%d dy1=%d...\n",
            res, page.dirty.y0, page.dirty.y1);
    fflush(stderr);

    if (!vbi_export_stdio(ctx->ex, stderr, &page)) {
        fprintf(stderr, "failed: %s\n", vbi_export_errstr(ctx->ex));
        //exit(EXIT_FAILURE);
    }
#endif

    // TODO: check if page changed
    if (page.columns!=ctx->last_cols || page.rows!=ctx->last_rows || 1) {
        vbi_subno subno;
        char *lang;
        vbi_page_type vpt = vbi_classify_page(ctx->vbi,
                                              ev->ev.ttx_page.pgno,
                                              &subno, &lang);
        int chop_top = ctx->force_chop_top ||
            ((page.rows > 1) && (vpt == VBI_SUBTITLE_PAGE));

        av_log(0, AV_LOG_DEBUG, "%d x %d page chop:%d\n",
               page.columns, page.rows, chop_top);

        ctx->sub_rect = av_mallocz(sizeof(*ctx->sub_rect));
        res = ctx->gen_bitmap ?
            gen_sub_bitmap(ctx, &page, chop_top) :
            gen_sub_text  (ctx, &page, chop_top);
        if (res)
            av_freep(&ctx->sub_rect);

        ctx->last_cols = page.columns;
        ctx->last_rows = page.rows;
    }
    vbi_unref_page(&page);
}

static int teletext_init_decoder(AVCodecContext *avctx)
{
    TeletextContext *ctx = avctx->priv_data;
    const char* p_env;

    ctx->sub_rect = 0;

    ctx->vbi = vbi_decoder_new();
    assert(NULL != ctx->vbi);
    vbi_event_handler_add(ctx->vbi, VBI_EVENT_TTX_PAGE, handler, ctx);
    ctx->dx = vbi_dvb_pes_demux_new (/* callback */ NULL, NULL);
    assert(NULL != ctx->dx);
#ifdef DEBUG
    {
        char *t;
        ctx->ex = vbi_export_new("text", &t);
    }
#endif
    av_log(avctx, AV_LOG_INFO, "teletext pgno:%s\n", ctx->pgno);
    p_env = getenv("TELETEXT_Y_OFFSET");
    if (p_env)
        ctx->y_offset = strtol(p_env, 0, 10);
    ctx->sub_display_time = 10000;
    ctx->ci_clip = 256; /* dvdsub encoder cannot take more than 16.. */
    return 0;
}

static int teletext_close_decoder(AVCodecContext *avctx)
{
    TeletextContext *ctx = avctx->priv_data;

#ifdef DEBUG
    av_log(avctx, AV_LOG_INFO,
           " Teletext: lines_total=%u\n", ctx->lines_processed);
#endif
    av_free(ctx->sub_rect);

    vbi_dvb_demux_delete(ctx->dx);
    vbi_decoder_delete(ctx->vbi);
    return 0;
}

#define OFFSET(x) offsetof(TeletextContext, x)
#define SD AV_OPT_FLAG_SUBTITLE_PARAM | AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
    {"teletext_page", "Number(s) of teletext page(s) to decode", OFFSET(pgno), AV_OPT_TYPE_STRING, {.str = "100"}, 0, 0, SD},
    {"teletext_chop_top", "Force discarding the top teletext line", OFFSET(force_chop_top), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, SD},
    {"teletext_bitmap", "Generate bitmap rather than text subtitles", OFFSET(gen_bitmap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, SD},
    {"teletext_y_offset", "y offset of generated bitmaps", OFFSET(y_offset), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1080, SD},
    { NULL },
};

static const AVClass teletext_class = {
    .class_name = "teletext",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

AVCodec ff_teletext_decoder = {
    .name      = "teletext",
    .type      = AVMEDIA_TYPE_SUBTITLE,
    .id        = CODEC_ID_DVB_TELETEXT,
    .priv_data_size = sizeof(TeletextContext),
    .init      = teletext_init_decoder,
    .close     = teletext_close_decoder,
    .decode    = teletext_decode_frame,
    .long_name = NULL_IF_CONFIG_SMALL("Teletext subtitles"),
    .priv_class= &teletext_class,
};

/* Local Variables: */
/* indent-tabs-mode:nil */
/* tab-width:4 */
/* c-basic-offset:4 */
/* End: */
